/*
 * contrib/spi/autoinc.c
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "utils/builtins.h"
#include "utils/rel.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(autoinc);

Datum autoinc(PG_FUNCTION_ARGS)
{
	TriggerData *fc_trigdata = (TriggerData *) fcinfo->context;
	Trigger    *fc_trigger;		/* 获取触发器名称 */
	int			fc_nargs;			/* 参数数量 */
	int		   *fc_chattrs;		/* 要更改的属性的 attnums */
	int			fc_chnattrs = 0;	/* 上述的数量 */
	Datum	   *fc_newvals;		/* 上述的值 */
	bool	   *fc_newnulls;		/* 上述的空值标志 */
	char	  **fc_args;			/* 参数 */
	char	   *fc_relname;		/* 触发的关系名称 */
	Relation	fc_rel;			/* 触发的关系 */
	HeapTuple	fc_rettuple = NULL;
	TupleDesc	fc_tupdesc;		/* 元组描述 */
	bool		fc_isnull;
	int			fc_i;

	if (!CALLED_AS_TRIGGER(fcinfo))
		/* 内部错误 */
		elog(ERROR, "not fired by trigger manager");
	if (!TRIGGER_FIRED_FOR_ROW(fc_trigdata->tg_event))
		/* 内部错误 */
		elog(ERROR, "must be fired for row");
	if (!TRIGGER_FIRED_BEFORE(fc_trigdata->tg_event))
		/* 内部错误 */
		elog(ERROR, "must be fired before event");

	if (TRIGGER_FIRED_BY_INSERT(fc_trigdata->tg_event))
		fc_rettuple = fc_trigdata->tg_trigtuple;
	else if (TRIGGER_FIRED_BY_UPDATE(fc_trigdata->tg_event))
		fc_rettuple = fc_trigdata->tg_newtuple;
	else
		/* 内部错误 */
		elog(ERROR, "cannot process DELETE events");

	fc_rel = fc_trigdata->tg_relation;
	fc_relname = SPI_getrelname(fc_rel);

	fc_trigger = fc_trigdata->tg_trigger;

	fc_nargs = fc_trigger->tgnargs;
	if (fc_nargs <= 0 || fc_nargs % 2 != 0)
		/* 内部错误 */
		elog(ERROR, "autoinc (%s): even number gt 0 of arguments was expected", fc_relname);

	fc_args = fc_trigger->tgargs;
	fc_tupdesc = fc_rel->rd_att;

	fc_chattrs = (int *) palloc(fc_nargs / 2 * sizeof(int));
	fc_newvals = (Datum *) palloc(fc_nargs / 2 * sizeof(Datum));
	fc_newnulls = (bool *) palloc(fc_nargs / 2 * sizeof(bool));

	for (fc_i = 0; fc_i < fc_nargs;)
	{
		int			fc_attnum = SPI_fnumber(fc_tupdesc, fc_args[fc_i]);
		int32		fc_val;
		Datum		fc_seqname;

		if (fc_attnum <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
					 errmsg("\"%s\" has no attribute \"%s\"",
							fc_relname, fc_args[fc_i])));

		if (SPI_gettypeid(fc_tupdesc, fc_attnum) != INT4OID)
			ereport(ERROR,
					(errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION),
					 errmsg("attribute \"%s\" of \"%s\" must be type INT4",
							fc_args[fc_i], fc_relname)));

		fc_val = DatumGetInt32(SPI_getbinval(fc_rettuple, fc_tupdesc, fc_attnum, &fc_isnull));

		if (!fc_isnull && fc_val != 0)
		{
			fc_i += 2;
			continue;
		}

		fc_i++;
		fc_chattrs[fc_chnattrs] = fc_attnum;
		fc_seqname = CStringGetTextDatum(fc_args[fc_i]);
		fc_newvals[fc_chnattrs] = DirectFunctionCall1(nextval, fc_seqname);
		/* nextval 现在返回 int64; 强制转换为 int32 */
		fc_newvals[fc_chnattrs] = Int32GetDatum((int32) DatumGetInt64(fc_newvals[fc_chnattrs]));
		if (DatumGetInt32(fc_newvals[fc_chnattrs]) == 0)
		{
			fc_newvals[fc_chnattrs] = DirectFunctionCall1(nextval, fc_seqname);
			fc_newvals[fc_chnattrs] = Int32GetDatum((int32) DatumGetInt64(fc_newvals[fc_chnattrs]));
		}
		fc_newnulls[fc_chnattrs] = false;
		pfree(DatumGetTextPP(fc_seqname));
		fc_chnattrs++;
		fc_i++;
	}

	if (fc_chnattrs > 0)
	{
		fc_rettuple = heap_modify_tuple_by_cols(fc_rettuple, fc_tupdesc,
											 fc_chnattrs, fc_chattrs,
											 fc_newvals, fc_newnulls);
	}

	pfree(fc_relname);
	pfree(fc_chattrs);
	pfree(fc_newvals);
	pfree(fc_newnulls);

	return PointerGetDatum(fc_rettuple);
}
